﻿// Copyright (c) Inossem. All rights reserved.
// Licensed under the LGPL-3.0 license. See LICENSE file in the project root for full license information.
using DWF.Studio.ProcessExecutor.Core.Process;
using DWF.Studio.ProcessExecutor.Core.Utils;
using System;
using System.Activities;
using System.Activities.XamlIntegration;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace DWF.Studio.ProcessExecutor.Core.Engine
{
    internal class WorkflowEngine
    {
        //AppDomain.CurrentDomain.AssemblyResolve += assemblyResolve;

        private const string XamlXmlnsRoot = "http://schemas.microsoft.com/netfx/2009/xaml/activities";
        private const string XamlXmlnsX = "http://schemas.microsoft.com/winfx/2006/xaml";

        private WorkflowApplication _wfApplication = null;

        private string[] _assemblyResolutionPaths = null;

        private IThreadBlock threadBlock = null;

        public WorkflowEngine()
        {
            AppDomain.CurrentDomain.AssemblyResolve += assemblyResolve;
            threadBlock = new AutoThreadBlock();
        }

        public void ExecuteProcess(ProcessInfo processInfo)
        {
            _assemblyResolutionPaths = processInfo.Dependency.ToArray();

            Thread thread = new Thread(new ThreadStart(() =>
            {
                try
                {
                    excute(processInfo.Id, processInfo.Directory, processInfo.Start);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    if (Share.Log.SystemEventLog.CreateSystemEventLogCategory("Application", "ProcessGo"))
                    {
                        Share.Log.SystemEventLog.WriteSystemEventLog("Application", ex.Message, EventLogEntryType.Error);
                    }
                    throw ex;
                }
            }));

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start();

            threadBlock.Block();
        }

        private void excute(string projectId, string processDirectory, string scriptPath)
        {
            var script = handleArguments(scriptPath);
            MemoryStream workflowStream = new MemoryStream(ASCIIEncoding.Unicode.GetBytes(script));
            ActivityXamlServicesSettings settings = new ActivityXamlServicesSettings
            {
                CompileExpressions = true
            };

            var activityExecute = ActivityXamlServices.Load(workflowStream, settings);

            #region 默认导入参数
            Dictionary<String, Object> inputs = null;
            DynamicActivity dynamicActivity = activityExecute as DynamicActivity;
            if (dynamicActivity != null)
            {
                inputs = new Dictionary<string, object>();
                var hasScriptPath = dynamicActivity.Properties.Any(item => item.Name == "ScriptPath" && item.Type.Equals(typeof(InArgument<String>)));
                var hasProjectDirectory = dynamicActivity.Properties.Any(item => item.Name == "ProjectDirectory" && item.Type.Equals(typeof(InArgument<String>)));

                //工程目录
                if (hasProjectDirectory && !String.IsNullOrWhiteSpace(processDirectory))
                {
                    inputs.Add("ProjectDirectory", (object)(processDirectory + Path.DirectorySeparatorChar));
                }

                //脚本文件
                if (hasScriptPath)
                {
                    inputs.Add("ScriptPath", scriptPath);
                }
            }

            #endregion

            using (var syncContext = new QueueSynchronizationContext())
            {
                if (inputs != null && inputs.Count > 0)
                {
                    _wfApplication = new WorkflowApplication(activityExecute, inputs);
                }
                else
                {
                    _wfApplication = new WorkflowApplication(activityExecute);
                }

                _wfApplication.SynchronizationContext = syncContext;

                _wfApplication.Extensions.Add(new WorkflowRuntimeService(projectId, processDirectory, scriptPath));

                var executionLog = new WorkflowTrackingParticipant((message) =>
                {
                    Console.WriteLine(message);
                });
                _wfApplication.Extensions.Add(executionLog);

                _wfApplication.Aborted = (e) =>
                {
                    Console.WriteLine(e.Reason.Message);
                    syncContext.Complete();
                };

                _wfApplication.OnUnhandledException = (e) =>
                {
                    Console.WriteLine(e.UnhandledException.Message);
                    
                    return UnhandledExceptionAction.Terminate;
                };

                _wfApplication.Completed = (e) =>
                {
                    syncContext.Complete();
                };

                _wfApplication.Run();

                syncContext.Run();

                threadBlock.Continue();
            }
        }

        private string handleArguments(string scriptPath)
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(scriptPath);
            var xmlnsm = new XmlNamespaceManager(xmldoc.NameTable);
            xmlnsm.AddNamespace("xaroot", XamlXmlnsRoot);
            xmlnsm.AddNamespace("x", XamlXmlnsX);

            var activity_node = xmldoc.SelectSingleNode("/xaroot:Activity", xmlnsm);
            if (activity_node != null)
            {
                var members_node = activity_node.SelectSingleNode("x:Members", xmlnsm);
                if (members_node == null)
                {
                    members_node = xmldoc.CreateElement("x", "Members", XamlXmlnsX);
                    activity_node.AppendChild(members_node);
                }

                var projectDirectory_node = members_node.SelectSingleNode("x:Property[@Name=\"ProjectDirectory\"]", xmlnsm);
                if (projectDirectory_node == null)
                {
                    var property_node = xmldoc.CreateElement("x", "Property", XamlXmlnsX);
                    property_node.SetAttribute("Name", "ProjectDirectory");
                    property_node.SetAttribute("Type", "InArgument(x:String)");

                    members_node.AppendChild(property_node);
                }

                var scriptPath_node = members_node.SelectSingleNode("x:Property[@Name=\"ScriptPath\"]", xmlnsm);
                if (scriptPath_node == null)
                {
                    var property_node = xmldoc.CreateElement("x", "Property", XamlXmlnsX);
                    property_node.SetAttribute("Name", "ScriptPath");
                    property_node.SetAttribute("Type", "InArgument(x:String)");

                    members_node.AppendChild(property_node);
                }
            }

            return xmldoc.InnerXml;
        }

        private Assembly assemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly asm in loadedAssemblies)
            {
                if (asm.FullName.Equals(args.Name))
                    return asm;
            }

            AssemblyName asmInfo = new AssemblyName(args.Name);
            if (_assemblyResolutionPaths != null)
            {
                for (int i = 0; i < _assemblyResolutionPaths.Length; i++)
                {
                    string asmFilePath = Path.Combine(_assemblyResolutionPaths[i], asmInfo.Name + ".dll");
                    if (File.Exists(asmFilePath))
                    {
                        return Assembly.LoadFrom(asmFilePath);
                    }
                    else
                    {
                        asmFilePath = Path.Combine(_assemblyResolutionPaths[i], asmInfo.Name + ".exe");
                        if (File.Exists(asmFilePath))
                            return Assembly.LoadFrom(asmFilePath);
                    }
                }
            }

            return null;
        }
    }
}
